package org.Yiran.timetale_re.tilerender.blockrender;

import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Axis;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.MapItem;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.client.model.data.ModelData;
import org.Yiran.timetale_re.block.decorate.longtable.LongTable;
import org.Yiran.timetale_re.item.runeblueprint.base.RuneBlueprintItem;
import org.Yiran.timetale_re.tile.blocktile.LongTableBlockEntity;
import org.jetbrains.annotations.NotNull;

/**
 * 长桌方块实体渲染器
 * <p>
 * 负责渲染长桌方块实体上放置的物品，根据不同物品类型采用不同的渲染策略。
 * 支持标准物品、地图和符文图纸等特殊物品的渲染。
 * </p>
 * 
 * @author Yiran
 * @version 1.0
 */
public class LongTableBlockEntityRenderer implements BlockEntityRenderer<LongTableBlockEntity> {

    /**
     * 构造函数
     * <p>
     * 初始化长桌方块实体渲染器。
     * </p>
     * 
     * @param context 方块实体渲染器提供上下文
     */
    public LongTableBlockEntityRenderer(BlockEntityRendererProvider.Context context) {
    }

    /**
     * 渲染长桌方块实体
     * <p>
     * 根据长桌的朝向，依次渲染左右两个部分放置的物品。
     * </p>
     * 
     * @param blockEntity 长桌方块实体
     * @param partialTick 部分刻时间（用于平滑动画）
     * @param poseStack 姿态矩阵栈
     * @param bufferSource 多重缓冲源
     * @param packedLight 打包光照值
     * @param packedOverlay 打包叠加值
     */
    @Override
    public void render(@NotNull LongTableBlockEntity blockEntity, float partialTick, @NotNull PoseStack poseStack,
                       @NotNull MultiBufferSource bufferSource, int packedLight, int packedOverlay) {
        // 获取方块状态和朝向
        BlockState blockState = blockEntity.getBlockState();
        Direction facing = blockState.getValue(LongTable.FACING);

        // 渲染左半部分物品
        renderPartItem(blockEntity, poseStack, bufferSource, packedLight, facing, LongTable.LEFT_PART);

        // 渲染右半部分物品
        renderPartItem(blockEntity, poseStack, bufferSource, packedLight, facing, LongTable.RIGHT_PART);
    }

    // ==================== 物品渲染方法 ====================

    /**
     * 渲染部件物品
     * <p>
     * 渲染长桌指定部分的物品，如果该部分有物品则进行渲染。
     * </p>
     * 
     * @param blockEntity 长桌方块实体
     * @param poseStack 姿态矩阵栈
     * @param bufferSource 多重缓冲源
     * @param packedLight 打包光照
     * @param facing 朝向
     * @param partId 部件ID
     */
    private void renderPartItem(LongTableBlockEntity blockEntity, PoseStack poseStack,
                                MultiBufferSource bufferSource, int packedLight, Direction facing, int partId) {
        ItemStack item = blockEntity.getItem(partId);
        if (!item.isEmpty()) {
            float rotation = blockEntity.getItemRotation(partId);
            renderItem(blockEntity, poseStack, bufferSource, packedLight, facing, item, partId, rotation);
        }
    }

    /**
     * 渲染单个物品
     * <p>
     * 根据物品类型选择合适的渲染方法进行渲染。
     * </p>
     * 
     * @param blockEntity 长桌方块实体
     * @param poseStack 姿态矩阵栈
     * @param bufferSource 多重缓冲源
     * @param packedLight 打包光照
     * @param facing 朝向
     * @param itemStack 物品堆
     * @param partId 部件ID
     * @param rotation 旋转角度
     */
    private void renderItem(LongTableBlockEntity blockEntity, PoseStack poseStack,
                            MultiBufferSource bufferSource, int packedLight, Direction facing,
                            ItemStack itemStack, int partId, float rotation) {
        poseStack.pushPose();

        // 根据朝向调整位置
        Vec3 pos = getItemPositionWithOffset(blockEntity, partId, facing);
        poseStack.translate(pos.x, pos.y, pos.z);

        // 旋转物品使其面向正确方向
        poseStack.mulPose(Axis.YP.rotationDegrees(-facing.toYRot()));

        // 根据物品类型选择不同的渲染方式
        if (itemStack.getItem() instanceof MapItem) {
            renderMapItem(blockEntity, poseStack, bufferSource, packedLight, itemStack, rotation, partId, facing);
        } else if (itemStack.getItem() instanceof RuneBlueprintItem) {
            renderRuneBlueprintItem(blockEntity, poseStack, bufferSource, packedLight, itemStack, rotation, partId, facing);
        } else {
            renderStandardItem(poseStack, bufferSource, packedLight, itemStack, facing, rotation);
        }

        poseStack.popPose();
    }

    // ==================== 特殊物品渲染方法 ====================

    /**
     * 渲染地图物品
     * <p>
     * 专门用于渲染地图物品，使其平放在长桌上并正确显示地图内容。
     * </p>
     * 
     * @param blockEntity 长桌方块实体
     * @param poseStack 姿态矩阵栈
     * @param bufferSource 多重缓冲源
     * @param packedLight 打包光照
     * @param itemStack 物品堆
     * @param rotation 旋转角度
     * @param partId 部件ID
     * @param facing 朝向
     */
    private void renderMapItem(LongTableBlockEntity blockEntity, PoseStack poseStack,
                               MultiBufferSource bufferSource, int packedLight,
                               ItemStack itemStack, float rotation, int partId, Direction facing) {
        prepareCenteredItemRendering(poseStack, blockEntity, partId, facing, rotation);

        // 地图平放并居中显示
        poseStack.translate(-0.50, 0.225, -0.50); // 先平移
        poseStack.scale(0.00782f, 0.00782f, 0.00782f); // 后缩放
        poseStack.mulPose(Axis.XP.rotationDegrees(90)); // 平放

        // 地图不使用旋转
        Integer mapId = MapItem.getMapId(itemStack);
        if (mapId == null) return;

        MapItemSavedData mapData = getMapData(blockEntity, itemStack);
        if (mapData == null) return;

        // 使用原版地图渲染器渲染地图
        Minecraft.getInstance().gameRenderer.getMapRenderer().render(poseStack, bufferSource, mapId, mapData, false, packedLight);
    }

    /**
     * 渲染符文图纸物品
     * <p>
     * 专门用于渲染符文图纸物品，使用自定义模型进行渲染。
     * </p>
     * 
     * @param blockEntity 长桌方块实体
     * @param poseStack 姿态矩阵栈
     * @param bufferSource 多重缓冲源
     * @param packedLight 打包光照
     * @param itemStack 物品堆
     * @param rotation 旋转角度
     * @param partId 部件ID
     * @param facing 朝向
     */
    private void renderRuneBlueprintItem(LongTableBlockEntity blockEntity, PoseStack poseStack,
                                         MultiBufferSource bufferSource, int packedLight,
                                         ItemStack itemStack, float rotation, int partId, Direction facing) {
        prepareCenteredItemRendering(poseStack, blockEntity, partId, facing, rotation);

        // 获取符文类型
        String runeType = RuneBlueprintItem.getRuneType(itemStack);

        Minecraft mc = Minecraft.getInstance();

        // 使用注册时相同的路径
        ResourceLocation modelLocation = ResourceLocation.fromNamespaceAndPath("timetale_re",
                "block/rune_blueprint/rune_blueprint_" + runeType);

        // 尝试加载模型
        BakedModel model = mc.getModelManager().getModel(modelLocation);

        // 使用默认的方块渲染类型，让模型自己处理纹理
        VertexConsumer vertexConsumer = bufferSource.getBuffer(RenderType.cutoutMipped());

        // 平放并居中显示
        poseStack.translate(-0.3, -0.07, -0.3);
        poseStack.scale(0.6f, 0.6f, 0.6f);

        if (model != mc.getModelManager().getMissingModel()) {
            mc.getBlockRenderer().getModelRenderer().renderModel(
                    poseStack.last(),
                    vertexConsumer,
                    null,
                    model,
                    1.0F, 1.0F, 1.0F,
                    packedLight,
                    OverlayTexture.NO_OVERLAY,
                    ModelData.EMPTY,
                    RenderType.cutoutMipped()
            );
        }
    }

    /**
     * 渲染标准物品
     * <p>
     * 渲染普通物品，使用Minecraft标准的物品渲染方式。
     * </p>
     * 
     * @param poseStack 姿态矩阵栈
     * @param bufferSource 多重缓冲源
     * @param packedLight 打包光照
     * @param itemStack 物品堆
     * @param facing 朝向
     * @param rotation 旋转角度
     */
    private void renderStandardItem(PoseStack poseStack, MultiBufferSource bufferSource,
                                    int packedLight, ItemStack itemStack, Direction facing, float rotation) {
        net.minecraft.client.renderer.entity.ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer();
        BakedModel itemModel = itemRenderer.getModel(itemStack, null, null, 0);

        poseStack.scale(0.5f, 0.5f, 0.5f);
        poseStack.translate(0, 0.5, 0);
        poseStack.mulPose(Axis.XP.rotationDegrees(90));

        float facingRotation = facing.toYRot();
        float relativeRotation = rotation - facingRotation;
        poseStack.mulPose(Axis.ZP.rotationDegrees(relativeRotation));

        itemRenderer.render(itemStack, ItemDisplayContext.FIXED, false, poseStack, bufferSource, packedLight,
                OverlayTexture.NO_OVERLAY, itemModel);
    }

    // ==================== 辅助方法 ====================

    /**
     * 准备居中物品渲染的变换矩阵
     * <p>
     * 为居中渲染物品准备姿态矩阵，设置正确的旋转中心点。
     * </p>
     * 
     * @param poseStack 姿态矩阵栈
     * @param blockEntity 长桌方块实体
     * @param partId 部件ID
     * @param facing 朝向
     * @param rotation 旋转角度
     */
    private void prepareCenteredItemRendering(PoseStack poseStack, LongTableBlockEntity blockEntity,
                                            int partId, Direction facing, float rotation) {
        poseStack.popPose();
        poseStack.pushPose();

        // 获取桌子中心位置作为旋转中心
        Vec3 tableCenter = getTableCenterPosition(blockEntity, partId, facing);
        poseStack.translate(tableCenter.x, tableCenter.y, tableCenter.z);

        // 使用物品的旋转角度，以90度为增量进行旋转
        float adjustedRotation = Math.round(rotation / 90f) * 90f;
        poseStack.mulPose(Axis.YP.rotationDegrees(-adjustedRotation));
    }

    /**
     * 获取地图数据
     * <p>
     * 从世界中获取地图物品的保存数据。
     * </p>
     * 
     * @param blockEntity 长桌方块实体
     * @param itemStack 物品堆
     * @return 地图保存数据
     */
    private MapItemSavedData getMapData(LongTableBlockEntity blockEntity, ItemStack itemStack) {
        if (blockEntity.getLevel() != null) {
            return MapItem.getSavedData(itemStack, blockEntity.getLevel());
        }
        return null;
    }

    /**
     * 获取桌子中心位置
     * <p>
     * 根据长桌的朝向和部件ID计算桌子的中心位置。
     * </p>
     * 
     * @param blockEntity 长桌方块实体
     * @param partId 部件ID
     * @param facing 朝向
     * @return 中心位置向量
     */
    private Vec3 getTableCenterPosition(LongTableBlockEntity blockEntity, int partId, Direction facing) {
        // 计算当前部分的中心点
        return switch (facing) {
            case NORTH -> partId == LongTable.LEFT_PART ?
                    new Vec3(0.5, 0.78, 0.5) : new Vec3(1.5, 0.78, 0.5);
            case SOUTH -> partId == LongTable.LEFT_PART ?
                    new Vec3(0.5, 0.78, 0.5) : new Vec3(-0.5, 0.78, 0.5);
            case WEST -> partId == LongTable.LEFT_PART ?
                    new Vec3(0.5, 0.78, 0.5) : new Vec3(0.5, 0.78, -0.5);
            case EAST -> partId == LongTable.LEFT_PART ?
                    new Vec3(0.5, 0.78, 0.5) : new Vec3(0.5, 0.78, 1.5);
            default -> new Vec3(0.5, 0.78, 0.5);
        };
    }

    // ==================== 位置计算相关方法 ====================

    /**
     * 获取带偏移的物品位置
     * <p>
     * 根据物品的偏移量和长桌的朝向计算物品在世界中的最终位置。
     * </p>
     * 
     * @param blockEntity 长桌方块实体
     * @param partId 部件ID
     * @param facing 朝向
     * @return 物品位置向量
     */
    private Vec3 getItemPositionWithOffset(LongTableBlockEntity blockEntity, int partId, Direction facing) {
        // 获取基础位置
        Vec3 basePos = getItemPosition(partId, facing);

        // 获取物品偏移
        float itemXOffset = blockEntity.getItemXOffset(partId);
        float itemZOffset = blockEntity.getItemZOffset(partId);

        // 根据朝向调整偏移坐标系
        float adjustedXOffset, adjustedZOffset;
        adjustedXOffset = calculateAdjustedXOffset(facing, itemXOffset, itemZOffset);
        adjustedZOffset = calculateAdjustedZOffset(facing, itemXOffset, itemZOffset);

        // 应用偏移，但限制在合理范围内
        double finalX = basePos.x + adjustedXOffset;
        double finalZ = basePos.z + adjustedZOffset;

        // 限制物品在正确的方块内
        return constrainItemPosition(partId, facing, basePos, finalX, finalZ);
    }

    /**
     * 获取物品基础位置
     * <p>
     * 获取物品在长桌上的基础位置（不考虑偏移量）。
     * </p>
     * 
     * @param partId 部件ID
     * @param facing 朝向
     * @return 基础位置向量
     */
    private Vec3 getItemPosition(int partId, Direction facing) {
        // 基础位置在方块中心上方
        return new Vec3(0.5, 0.78, 0.5);
    }

    /**
     * 计算调整后的X偏移
     * <p>
     * 根据长桌的朝向调整物品的X轴偏移量。
     * </p>
     * 
     * @param facing 朝向
     * @param itemXOffset X偏移
     * @param itemZOffset Z偏移
     * @return 调整后的X偏移
     */
    private float calculateAdjustedXOffset(Direction facing, float itemXOffset, float itemZOffset) {
        return switch (facing) {
            case NORTH -> itemZOffset;
            case SOUTH -> -itemZOffset;
            case EAST -> -itemXOffset;
            default -> itemXOffset;
        };
    }

    /**
     * 计算调整后的Z偏移
     * <p>
     * 根据长桌的朝向调整物品的Z轴偏移量。
     * </p>
     * 
     * @param facing 朝向
     * @param itemXOffset X偏移
     * @param itemZOffset Z偏移
     * @return 调整后的Z偏移
     */
    private float calculateAdjustedZOffset(Direction facing, float itemXOffset, float itemZOffset) {
        return switch (facing) {
            case NORTH -> -itemXOffset;
            case SOUTH -> itemXOffset;
            case EAST -> -itemZOffset;
            default -> itemZOffset;
        };
    }

    /**
     * 约束物品位置在正确方块内
     * <p>
     * 确保物品位置不会超出长桌的范围。
     * </p>
     * 
     * @param partId 部件ID
     * @param facing 朝向
     * @param basePos 基础位置
     * @param finalX 最终X坐标
     * @param finalZ 最终Z坐标
     * @return 约束后的位置
     */
    private Vec3 constrainItemPosition(int partId, Direction facing, Vec3 basePos, double finalX, double finalZ) {
        if (partId == LongTable.LEFT_PART) {
            finalX = Math.max(0.2, Math.min(0.8, finalX));
            finalZ = Math.max(0.2, Math.min(0.8, finalZ));
        } else {
            // 对于右半部分，需要根据朝向限制在正确的方块内
            switch (facing) {
                case NORTH -> {
                    // 右半部分在 +X 方向
                    finalX = Math.max(1.2, Math.min(1.8, finalX));
                    finalZ = Math.max(0.2, Math.min(0.8, finalZ));
                }
                case SOUTH -> {
                    // 右半部分在 -X 方向
                    finalX = Math.max(-0.8, Math.min(-0.2, finalX));
                    finalZ = Math.max(0.2, Math.min(0.8, finalZ));
                }
                case WEST -> {
                    // 右半部分在 -Z 方向
                    finalX = Math.max(0.2, Math.min(0.8, finalX));
                    finalZ = Math.max(-0.8, Math.min(-0.2, finalZ));
                }
                case EAST -> {
                    // 右半部分在 +Z 方向
                    finalX = Math.max(0.2, Math.min(0.8, finalX));
                    finalZ = Math.max(1.2, Math.min(1.8, finalZ));
                }
            }
        }

        return new Vec3(finalX, basePos.y, finalZ);
    }

    /**
     * 是否在屏幕外渲染
     * <p>
     * 指示方块实体是否应该在屏幕外渲染，返回true表示即使方块不在视锥体内也会被渲染。
     * </p>
     * 
     * @param blockEntity 长桌方块实体
     * @return 是否在屏幕外渲染
     */
    @Override
    public boolean shouldRenderOffScreen(@NotNull LongTableBlockEntity blockEntity) {
        return true;
    }
}